﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Threading;
using HeritageAcademy.Models.ViewModels;
using HeritageAcademy.Models.DataModels;

namespace HeritageAcademy.Library.Controls
{
    /// <summary>
    /// Interaction logic for BookView.xaml
    /// </summary>
    public partial class BookViewer : ICoursePaginator
    {
        #region Parameters
        ChapterViewModelCollection _chapters;
        int _currentViewerChapter = -1;
        private CourseLocation _delayedLocation;
        private DispatcherTimer _locationTimer;
        private CourseLocation _nextLocation;
        List<DynamicDocumentPaginator> _paginators;
        CourseViewModel _viewModel;
        private const double _positionCharacterCount = 1000.0;
        int _p;
        #endregion Fields

        #region Ctor
        public BookViewer()
        {
            InitializeComponent();

            DataContextChanged += BookView_DataContextChanged;
            SizeChanged += BookView_SizeChanged;
            AddHandler(FlowDocumentPageViewerLight.SelectionChangedEvent, new RoutedEventHandler(ViewerSelectionChanged));
            AddHandler(FlowDocumentPageViewerLight.ZoomChangedEvent, new RoutedEventHandler(ViewerZoomChanged));

            Loaded += (sender, e) => MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
        }
        #endregion Constructors

        #region Properties
        public ChapterViewModel CurrentChapter
        {
            get
            {
                if (CurrentLocation == null) return null;
                if (_chapters == null || CurrentLocation.Chapter > _chapters.Count - 1) return null;
                return _chapters[CurrentLocation.Chapter];
            }
        }

        public FlowDocumentPageViewer PageViewer
        {
            get { return Viewer; }
        }
        #endregion Properties

        #region Event Handlers
        void BookView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            // TODO: Initially the expectation was for this to be a View over a BookViewModel object. It's
            // become much more like a control so it was moved into the UserControls namespace and we're
            // treating it more like a control now. We should clean this up, however, to no longer depend
            // on a specific ViewModel to funciton correctly but behave more like a real control.

            // make sure that we don't keep references to this around on the old viewModel
            if (e.OldValue != null)
                ((CourseViewModel) e.OldValue).Paginator = null;

            //SetPositionCount(1);
            SetPercentage(1);
            SetCurrentPosition(1);

            // clean the current document
            Viewer.Document = null;
            _currentViewerChapter = -1;

            // grab areference to the new viewModel
            _viewModel = e.NewValue as CourseViewModel;

            // initialize the paginators collection no matter what (we want to release any old paginators)
            _paginators = new List<DynamicDocumentPaginator>();

            // return if our viewModel is null
            if (_viewModel == null)
            {
                _chapters = null;
                return;
            }

            // set this control to be the paginator for the new ViewModel 
            _viewModel.Paginator = this;
            SetIsPaginationValid(false);

            var chapterIndex = 0;
            _chapters = _viewModel.FlatChapters;
            
            // enumerate our chapters
            if (_chapters != null && _chapters.Count > 0)
            {
                var backgroundWorker = new BackgroundWorker();

                DoWorkEventHandler dowork = null;
                dowork = delegate(object s1, DoWorkEventArgs args1)
                {
                    Action a = () =>
                    {
                        var ch = args1.Argument as ChapterViewModel;

                        if (ch == null) return;
                        var p = (ch.Document as IDocumentPaginatorSource).DocumentPaginator as DynamicDocumentPaginator;
                        _paginators.Add(p);

                        SetPositionCount(PositionCount + DocumentLengthToPositionCount(ch.DocumentLength));
                    };
                    Dispatcher.Invoke(a);
                };

                RunWorkerCompletedEventHandler doworkCompleted = null;
                doworkCompleted = delegate(object s2, RunWorkerCompletedEventArgs args2)
                 {
                     Action a = () =>
                     {
                         if (_chapters == null) return;
                         RaiseBookLoadingProgressChanged((int)Math.Round(100 * (double)chapterIndex / _chapters.Count));
                         chapterIndex++;

                         if (chapterIndex < _chapters.Count)
                         {
                             backgroundWorker.RunWorkerAsync(_chapters[chapterIndex]);
                         }
                         else
                         {
                             // all of the chapters have been loaded
                             backgroundWorker.RunWorkerCompleted -= doworkCompleted;
                             backgroundWorker.DoWork -= dowork;

                             foreach (var p in _paginators)
                             {
                                 p.PaginationProgress += p_PaginationProgress;
                                 p.PagesChanged += p_PagesChanged;
                             }

                             RaiseBookLoadingProgressChanged(100);
                             RaiseBookLoaded();

                             if (_viewModel.CurrentLocation != null)
                             {
                                 GotoLocation(_viewModel.CurrentLocation);
                                 UpdatePosition(CurrentLocation);
                             }
                             else
                             {
                                 GotoLocation(new CourseLocation(0, 0));
                             }

                             TryInvalidatePages();
                         }
                     };

                     // back on the UI thread
                     Dispatcher.Invoke(a);
                 };

                backgroundWorker.RunWorkerCompleted += doworkCompleted;
                backgroundWorker.DoWork += dowork;
                backgroundWorker.RunWorkerAsync(_chapters[chapterIndex]);
            }
        }

        void BookView_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            TryInvalidatePages();
        }

        void p_PagesChanged(object sender, PagesChangedEventArgs e)
        {
            TryInvalidatePages();
        }

        void p_PaginationProgress(object sender, PaginationProgressEventArgs e)
        {
            if (_paginators.Count <= 0) return;
            var p = sender as DynamicDocumentPaginator;
            if (p == null || !p.IsPageCountValid) return;
            p.IsBackgroundPaginationEnabled = false;
            TryUpdatePendingLocationChange();
            UpdateNextPage();

            // TODO: added this CurrentLoaction != null && but not sure if its right 
            if (CurrentLocation != null && _paginators[CurrentLocation.Chapter].Equals(p))
                RaisePageChanged();
        }
        #endregion Event Handlers

        #region Public Methods
        public double BookLocationToPercentage(CourseLocation b)
        {
            var position = BookLocationToPosition(b);
            return position / (double)PositionCount;
        }

        public int BookLocationToPosition(CourseLocation b)
        {
            if (_chapters != null)
            {
                var c = CoerceChapterIndex(b.Chapter);
                var p = 0;

                // add up the lengths of all preceding chapters
                for (var i = 0; i < c; i++) p += DocumentLengthToPositionCount(_chapters[i].DocumentLength);

                // add the partial length of the chapter referred to by b
                p += DocumentLengthToPositionCount((int)(_chapters[c].DocumentLength * b.ChapterLocation));

                return p;
            }
            return 0;
        }

        public ChapterViewModel GetChapter(int ch)
        {
            if (_chapters != null && _chapters.Count > 0)
                return _chapters[CoerceChapterIndex(ch)];

            return null;
        }

        public string GetSelectedText()
        {
            return Viewer.Selection != null ? Viewer.Selection.Text : null;
        }

        public void GotoPercentage(double percentage)
        {
            var position = (int)Math.Round(PositionCount * percentage);
            GotoLocation(PositionToBookLocation(position));
        }

        /// <summary>
        /// Determines if the provided BookLocation is visible on the current page. 
        /// </summary>
        public bool IsLocationOnCurrentPage(CourseLocation location)
        {
            var c = CoerceChapterIndex(location.Chapter);

            if (CurrentLocation.Chapter != c) return false;
            if (Viewer.PageCount == 0) return false;

            // get the chapter location of the start of the current page and the end of the next
            // keeping in mind that MasterPageNumber is a 1-based index

            var start = CurrentLocation.ChapterLocation;
            var end = start + (1.0 / Viewer.PageCount); //(double)(Viewer.MasterPageNumber) / (double)Viewer.PageCount;

            return location.ChapterLocation >= start && location.ChapterLocation < end;
        }

        public CourseLocation PercentageToBookLocation(double percentage)
        {
            return PositionToBookLocation(PercentageToPosition(percentage));
        }

        public int PercentageToPosition(double percentage)
        {
            return (int)Math.Round(PositionCount * percentage);
        }

        public CourseLocation PositionToBookLocation(int position)
        {
            if (_chapters == null || _chapters.Count == 0) return null;
            if (position <= 0) return new CourseLocation(0, 0);
            if (position >= PositionCount) return new CourseLocation(_chapters.Count - 1, 1.0);

            var ch = 0;
            var positionTotal = 0;

            while (positionTotal < position && ch < _chapters.Count)
            {
                positionTotal += ChapterIndexToPositionCount(ch);
                ch++;
            }

            // clean up the last chapter we counted
            ch--;
            positionTotal -= ChapterIndexToPositionCount(ch);

            var chapterOffset = position - positionTotal;
            var chapterLocation = chapterOffset / (double)ChapterIndexToPositionCount(ch);

            return new CourseLocation(ch, chapterLocation);
        }
        #endregion Public Methods

        #region Protected Methods
        protected override void OnMouseWheel(MouseWheelEventArgs e)
        {
            // If control is pressed we want zoom to work instead
            if (Keyboard.Modifiers == ModifierKeys.Control)
                return;

            if (e.Delta > 0)
                PreviousPage();
            else if (e.Delta < 0)
                NextPage();
        }
        #endregion Protected Methods

        #region Private Methods
        private int ChapterIndexToPositionCount(int chapterIndex)
        {
            var ch = CoerceChapterIndex(chapterIndex);
            return DocumentLengthToPositionCount(_chapters[ch].DocumentLength);
        }

        private int CoerceChapterIndex(int chapterIndex)
        {
            if (_chapters != null)
            {
                var r = chapterIndex;
                r = Math.Max(r, 0);
                r = Math.Min(r, _chapters.Count - 1);
                return r;
            }
            return 0;
        }

        private static int DocumentLengthToPositionCount(int p)
        {
            return (int)(p / _positionCharacterCount);
        }

        private DynamicDocumentPaginator GetNextPaginator()
        {
            if (_paginators != null && _paginators.Count > 0)
            {
                // check the paginator for the current chapter
                return _nextLocation != null && _paginators.Count > _nextLocation.Chapter &&
                       !_paginators[_nextLocation.Chapter].IsPageCountValid
                           ? _paginators[_nextLocation.Chapter]
                           : _paginators.FirstOrDefault(p => !p.IsPageCountValid);
            }

            return null;
        }

        private void RaiseBookLoadingProgressChanged(int progressPercentage)
        {
            Action action = () =>
            {
                if (BookLoadingProgressChanged != null)
                    BookLoadingProgressChanged(this, new ProgressChangedEventArgs(progressPercentage, null));
            };
            Dispatcher.Invoke(action, DispatcherPriority.Send);
        }

        private void TryInvalidatePages()
        {
            if (_paginators == null) return;
            
            // we need a real size in order to update the pages
            if (double.IsNaN(ActualWidth) || double.IsNaN(ActualHeight)) return;

            var pageSize = new Size(ActualWidth, ActualHeight);

            var isPageInfoValid = true;

            foreach (var p in _paginators)
            {
                if (!p.PageSize.Equals(pageSize))
                    p.PageSize = pageSize;

                if (p.IsPageCountValid) continue;
                isPageInfoValid = false;
                p.IsBackgroundPaginationEnabled = false;
            }

            if (isPageInfoValid) return;
            _p++;
            RaisePaginationStarted();
            SetIsPaginationValid(false);
            UpdateNextPage();
        }

        private void TryUpdatePendingLocationChange()
        {
            if (_nextLocation != null)
                GotoLocation(_nextLocation);
        }

        private void UpdateNextPage()
        {
            var p = GetNextPaginator();
            if (p != null)
            {
                // enabling background pagination appears to be the most lightweight
                // way of kicking off the pagination process
                p.IsBackgroundPaginationEnabled = true;
            }
            else
            {
                // calculate the new page count
                //var count = _paginators.Sum(paginator => paginator.PageCount);

                // update pagenumber stuff
                SetIsPaginationValid(true);

                // pagination is complete
                RaisePaginationCompleted();

                GotoLocation(CurrentLocation ?? new CourseLocation());
            }
        }

        private void UpdatePosition(CourseLocation location)
        {
            // update positions
            SetCurrentPosition(BookLocationToPosition(location));
            // update progress
            SetPercentage(BookLocationToPercentage(location));
        }

        void ViewerSelectionChanged(object sender, RoutedEventArgs e)
        {
            RaiseSelectionChanged();
        }

        void ViewerZoomChanged(object sender, RoutedEventArgs e)
        {
            //TryInvalidatePages();
        }
        #endregion Private Methods

        #region Location Management
        public void GotoLocation(CourseLocation b)
        {
            if (_paginators == null || b.Chapter > _paginators.Count - 1 || !_paginators[b.Chapter].IsPageCountValid)
            {
                // queue this chapter as the next one for pagination
                _nextLocation = b;
                return;
            }

            // clear any attempts to queue a pagination operation
            _nextLocation = null;

            // make sure we have a valid chapter number
            var ch = CoerceChapterIndex(b.Chapter);

            // see if we have the right document loaded, if not, then we do some timer stuff
            // in the TryGotoLocation function since we don't get proper events about when we're
            // able to navigate to the new page after the document has been loaded
            if (_currentViewerChapter != b.Chapter)
            {
                // load the document
                Viewer.Document = _chapters[ch].Document;
                _currentViewerChapter = ch;

                // hide the viewer to prevent flickers when we first load the new page
                Viewer.Opacity = 0;

                // begin the timer stuff
                TryGotoLocation(b);

                return;
            }

            // Check to see if the location is 1.0 and adjust. Here's the reasoning for this:
            // 1.0 is an easy way to jump to the end of the chapter but it isn't a valid location
            // because the location should always refer to the start of the content on the page
            // if we were actually at a chapter locaiton of 1.0, that would mean were actually
            // on the next page. Because we want people (including internal methods) to be able
            // to use 1.0 to jump to the end of chapter, we look for it here and then adjust
            // the location to represent a more accurate location. 
            if (b.ChapterLocation == 1.0)
                b.ChapterLocation = (double) (Viewer.PageCount - 1)/Viewer.PageCount;

            // update current location
            CurrentLocation = b;

            // otherwise, we already have the right document loaded so we can just navigate
            // our FlowDocumentViewer to the right page within the chapter
            var pageInViewer = (int)Math.Round(Viewer.PageCount * b.ChapterLocation) + 1;
            Viewer.GoToPage(pageInViewer);

            // make sure the viewer is visible (it may have been hidden before)
            Viewer.Opacity = 1;

            // do all the updates 
            RaiseLocationChanged();
            RaisePageChanged();
        }

        private void TryGotoLocation(CourseLocation b)
        {
            if (_locationTimer == null)
            {
                _locationTimer = new DispatcherTimer(DispatcherPriority.DataBind)
                                     {Interval = TimeSpan.FromMilliseconds(10)};
                _locationTimer.Tick += _locationTimer_Tick;
            }

            _locationTimer.Stop();
            _delayedLocation = b;   
            _locationTimer.Start();
        }

        void _locationTimer_Tick(object sender, EventArgs e)
        {
            if (Viewer == null || Viewer.Document == null)
            {
                _locationTimer.Stop();
                return;
            }

            if (Viewer.Document.DocumentPaginator.IsPageCountValid)
            {
                GotoLocation(_delayedLocation);
                _locationTimer.Stop();
                RaiseChapterChanged();
            }
        }

        public void NextPage()
        {
            if (_locationTimer != null && _locationTimer.IsEnabled) return;

            var b = new CourseLocation();

            if (CurrentLocation != null)
            {

                b.Chapter = CurrentLocation.Chapter;
                b.ChapterLocation = CurrentLocation.ChapterLocation + (1.0 / Viewer.PageCount);

                if (b.ChapterLocation >= 0.999)
                {
                    if (CurrentLocation.Chapter < (_chapters.Count - 1))
                    {
                        b.Chapter = CurrentLocation.Chapter + 1;
                        b.ChapterLocation = 0;
                    }
                    else
                    {
                        // we're at the end of the book
                        b.Chapter = _chapters.Count - 1;
                        b.ChapterLocation = 1.0;
                    }
                }
            }
            else
            {
                b.ChapterLocation = 1.0;
            }

            GotoLocation(b);
        }

        public void PreviousPage()
        {
            if (_locationTimer != null && _locationTimer.IsEnabled) return;

            var b = new CourseLocation();

            if (CurrentLocation != null)
            {

                b.Chapter = CurrentLocation.Chapter;
                b.ChapterLocation = CurrentLocation.ChapterLocation - (1.0 / Viewer.PageCount);

                if (b.ChapterLocation < 0)
                {
                    if (CurrentLocation.Chapter > 0)
                    {
                        b.Chapter = CurrentLocation.Chapter - 1;
                        b.ChapterLocation = 1.0;
                    }
                    else
                    {
                        // we're at the beginning of the book
                        b.Chapter = 0;
                        b.ChapterLocation = 0;
                    }
                }
            }

            GotoLocation(b);
        }
        #endregion

        #region PositionCount (Readonly)
        private static readonly DependencyPropertyKey PositionCountPropertyKey
            = DependencyProperty.RegisterReadOnly("PositionCount", typeof(int), typeof(BookViewer),
                new FrameworkPropertyMetadata(0));

        public static readonly DependencyProperty PositionCountProperty
            = PositionCountPropertyKey.DependencyProperty;

        public int PositionCount
        {
            get { return (int)GetValue(PositionCountProperty); }
        }

        protected void SetPositionCount(int value)
        {
            SetValue(PositionCountPropertyKey, value);
        }
        #endregion

        #region CurrentPosition (Readonly)
        private static readonly DependencyPropertyKey CurrentPositionPropertyKey
            = DependencyProperty.RegisterReadOnly("CurrentPosition", typeof(int), typeof(BookViewer),
                new FrameworkPropertyMetadata(0));

        public static readonly DependencyProperty CurrentPositionProperty
            = CurrentPositionPropertyKey.DependencyProperty;

        public int CurrentPosition
        {
            get { return (int)GetValue(CurrentPositionProperty); }
        }

        protected void SetCurrentPosition(int value)
        {
            SetValue(CurrentPositionPropertyKey, value);
        }
        #endregion

        #region IsPaginationValid (Readonly)
        private static readonly DependencyPropertyKey IsPaginationValidPropertyKey
            = DependencyProperty.RegisterReadOnly("IsPaginationValid", typeof(bool), typeof(BookViewer),
                new FrameworkPropertyMetadata(false));

        public static readonly DependencyProperty IsPaginationValidProperty
            = IsPaginationValidPropertyKey.DependencyProperty;

        public bool IsPaginationValid
        {
            get { return (bool)GetValue(IsPaginationValidProperty); }
        }

        protected void SetIsPaginationValid(bool value)
        {
            SetValue(IsPaginationValidPropertyKey, value);
        }
        #endregion

        #region CurrentLocation
        /// <summary>
        /// CurrentLocation Dependency Property
        /// </summary>
        public static readonly DependencyProperty CurrentLocationProperty =
            DependencyProperty.Register("CurrentLocation", typeof(CourseLocation), typeof(BookViewer),
                new FrameworkPropertyMetadata((CourseLocation)null,
                    new PropertyChangedCallback(OnCurrentLocationChanged)));

        /// <summary>
        /// Gets or sets the CurrentLocation property.  This dependency property 
        /// indicates ....
        /// </summary>
        public CourseLocation CurrentLocation
        {
            get { return (CourseLocation)GetValue(CurrentLocationProperty); }
            set { SetValue(CurrentLocationProperty, value); }
        }

        /// <summary>
        /// Handles changes to the CurrentLocation property.
        /// </summary>
        private static void OnCurrentLocationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((BookViewer)d).OnCurrentLocationChanged(e);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the CurrentLocation property.
        /// </summary>
        protected virtual void OnCurrentLocationChanged(DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue as CourseLocation == null) return;
            GotoLocation(e.NewValue as CourseLocation);
            UpdatePosition(e.NewValue as CourseLocation);
        }
        #endregion

        #region Percentage
        /// <summary>
        /// Percentage Read-Only Dependency Property
        /// </summary>
        private static readonly DependencyPropertyKey PercentagePropertyKey
            = DependencyProperty.RegisterReadOnly("Percentage", typeof(double), typeof(BookViewer),
                new FrameworkPropertyMetadata((double)0.0));

        public static readonly DependencyProperty PercentageProperty
            = PercentagePropertyKey.DependencyProperty;

        /// <summary>
        /// Gets the Percentage property.  This dependency property 
        /// indicates ....
        /// </summary>
        public double Percentage
        {
            get { return (double)GetValue(PercentageProperty); }
        }

        /// <summary>
        /// Provides a secure method for setting the Percentage property.  
        /// This dependency property indicates ....
        /// </summary>
        /// <param name="value">The new value for the property.</param>
        protected void SetPercentage(double value)
        {
            SetValue(PercentagePropertyKey, value);
        }
        #endregion

        #region Events
        public event ProgressChangedEventHandler BookLoadingProgressChanged;

        #region PageChanged

        /// <summary>
        /// Occurs whenever the page changes. This is more inclusive than LocationChanged because it also
        /// includes anything that causes repagination to occur.
        /// </summary>
        public event EventHandler PageChanged;

        private void RaisePageChanged()
        {
            if (PageChanged != null)
            {
                PageChanged(this, new EventArgs());
            }
        }

        #endregion
        #region LocationChanged

        /// <summary>
        /// Raised when the current location changes. The difference between this and PageChanged
        /// is that PageChanged occurs on repagination as well. LocationChanged only occurs when
        /// a request for a different location is processed.
        /// </summary>
        public event RoutedEventHandler LocationChanged;

        private void RaiseLocationChanged()
        {
            if (LocationChanged != null)
            {
                LocationChanged(this, new RoutedEventArgs());
            }
        }

        #endregion
        #region SelectionChanged

        /// <summary>
        /// A description of the event.
        /// </summary>
        public event RoutedEventHandler SelectionChanged;

        private void RaiseSelectionChanged()
        {
            if (SelectionChanged != null)
            {
                SelectionChanged(this, new RoutedEventArgs());
            }
        }

        #endregion
        #region ChapterChanged

        /// <summary>
        /// A description of the event.
        /// </summary>
        public event RoutedEventHandler ChapterChanged;

        private void RaiseChapterChanged()
        {
            if (ChapterChanged != null)
            {
                ChapterChanged(this, new RoutedEventArgs());
            }
        }

        #endregion
        #region BookLoaded

        /// <summary>
        /// Occurs once has a book has been loaded.
        /// </summary>
        public event EventHandler BookLoaded;

        private void RaiseBookLoaded()
        {
            if (BookLoaded != null)
            {
                BookLoaded(this, new EventArgs());
            }
        }

        #endregion
        #endregion

        #region IBookPaginator Members
        /// <summary>
        /// Occurs when the pagination process is completed
        /// </summary>
        public event RoutedEventHandler PaginationCompleted;

        private void RaisePaginationCompleted()
        {
            if (PaginationCompleted != null)
            {
                PaginationCompleted(this, new RoutedEventArgs());
            }
        }

        public event RoutedEventHandler PaginationStarted;
        private void RaisePaginationStarted()
        {
            if (PaginationStarted != null)
            {
                PaginationStarted(this, new RoutedEventArgs());
            }
        }
        #endregion
    }
}
